import fs from 'node:fs'
import path from 'node:path'
import stream from 'node:stream'

import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { domain, fileName } from 'src/utils'

import { UploadFileChunkDTO } from './upload.dto'
import * as uploadUtil from './upload.util'

@Injectable()
export class UploadService {
  /** 域名 */
  private domain: string
  /** 上传文件的地址 */
  private publicURI: string
  private readonly uploadPath = path.resolve('public/image')

  constructor(private configService: ConfigService<AppConfig>) {
    this.domain = domain(this.configService)
    this.publicURI = path.join('/static', '/image')
  }

  private mergeChunk(files: string[], originalname: string, writeStream: fs.WriteStream) {
    return new Promise((resolve, reject) => {
      const self = this
      const streamLoop = function (files: string[], writeStream: fs.WriteStream) {
        if (!files.length) {
          Logger.log('已合并完成')

          uploadUtil.deepRM(path.join(self.uploadPath, originalname))
          resolve('已合并完成')
          return
        }

        const current = files.shift()!
        const readStream = fs.createReadStream(path.join(self.uploadPath, originalname, current))
        readStream.pipe(writeStream, { end: false })

        readStream.on('end', () => {
          Logger.log(current, 'chunk已合并完毕')
          streamLoop(files, writeStream)
        })

        readStream.on('error', error => {
          Logger.warn(error, '合并文件失败')
          reject(error)
        })
      }

      streamLoop(files, writeStream)
    })
  }

  private writeChunk(file: Express.Multer.File, chunkPath: string, data: UploadFileChunkDTO) {
    return new Promise((resolve, reject) => {
      const writeStreamPath = path.join(chunkPath, uploadUtil.chunkName(file.originalname, data.index))

      const readStream = new stream.PassThrough()
      const writeStream = fs.createWriteStream(writeStreamPath)

      readStream.end(file.buffer)

      Logger.log(writeStreamPath, '写入块')

      readStream.on('data', (chunk: Buffer) => {
        Logger.log(`${chunk.length} 字节`, '开始写入chunk')
      })

      readStream.on('end', () => {
        Logger.log(`总共 ${data.index} 个chunk`, 'chunk已写入完毕')
        resolve(data.index)
      })

      readStream.on('error', error => {
        Logger.error('块写入失败', error)
        reject(error)
      })

      readStream.pipe(writeStream)
    })
  }

  public async uploadFile(file: Express.Multer.File, data: UploadFileChunkDTO) {
    const originalname = fileName(file.originalname)
    /**
     * 以上传的文件名创建文件夹
     */
    const filePath = path.join(this.uploadPath, originalname)

    /**
     * 校验 chunk 是否完整
     */
    if (uploadUtil.md5(file.buffer) !== data.hash) {
      throw new HttpException('chunk 损坏 请重新上传', HttpStatus.BAD_REQUEST)
    }

    if (data.index == 1) {
      if (!fs.existsSync(filePath)) fs.mkdirSync(filePath)
    }

    await this.writeChunk(file, filePath, data)

    if (data.index == data.total) {
      /**
       * 所有chunk
       */
      const chunk = uploadUtil.chunk(filePath)

      /**
       * 写入文件流
       */
      const writeStream = fs.createWriteStream(path.join(this.uploadPath, file.originalname))

      await this.mergeChunk(chunk, originalname, writeStream)

      return {
        ossUrl: this.domain + path.join(this.publicURI, file.originalname)
      }
    }

    return {
      process: data.index
    }
  }
}
